﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

// ============================================================================
/// <summary> Displays the decoded I²C communication. It shows also the states
/// for the Data and Clock lines </summary>
// ============================================================================

namespace I_C
{
    class ChannelDisplay
    {

        #region =================================== INTERNAL DATA
        // ========== Internal Que
        List<I2CNode> actionSeries;
        Dictionary<I2CDevice.OPERATION, float> drawWidth;

        // ========== The master device
        I2CMaster _master;
        // ========== The slave device

				
        #endregion

        #region =================================== Drawing info
        const int OFFSET_FROM_SCL = 40;
        PointF drawInfoPoint = new PointF();
        PointF drawBitPoint = new PointF();
        int countNodesDisplayed;
        float nodesWidth;
        I2CLine SCL;
        I2CLine SDA;

        // ========== Background
        Brush brush_BG = Brushes.Black;
        #endregion

        // ============================================================================
        /// <summary> Constructor </summary>
        // ============================================================================
        public ChannelDisplay()
        {
            actionSeries = new List<I2CNode>();
            drawWidth = new Dictionary<I2CDevice.OPERATION, float>();
            _master = new I2CMaster();
            SCL = new I2CLine();
            SDA = new I2CLine();

            SCL.Pen = Pens.Gold;
            SDA.Pen = Pens.GreenYellow;

            I2CLine.DELTA_Y = 0.7f * OFFSET_FROM_SCL;
            I2CLine.DELTA_X = 0.6f * OFFSET_FROM_SCL;

            drawWidth.Add(I2CDevice.OPERATION.ACK,      2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.NACK,     2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.START,    2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.STOP,     2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.RECEIVE,  2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.RESTART,  2 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.SEND,     16 * I2CLine.DELTA_X);
            drawWidth.Add(I2CDevice.OPERATION.SEND_ADDRESS, 16 * I2CLine.DELTA_X);
        }
        // ============================================================================
        /// <summary> Draws everything </summary>
        // ============================================================================
        public void Paint(Graphics g, int Width, int Height)
        {
            SDA.PointLeft = SCL.PointLeft = 0;
            ComputeCoords(Width, Height);
            
            countNodesDisplayed = actionSeries.Count;

            // ========== Counting how many nodes can be displayed
            nodesWidth = 0;
            for (int i = actionSeries.Count - 1; i >= 0; i--)
            {
                if (nodesWidth + drawWidth[actionSeries[i].Operation] > Width)
                {
                    break;
                }
                nodesWidth += drawWidth[actionSeries[i].Operation];
                countNodesDisplayed--;
            }
            I2CLine.DrawVerticalAxe(g, SDA.PointLeft, Height);
            for (int i = countNodesDisplayed; i < actionSeries.Count; i++)
            {
                if (SCL.PointLeft > Width - I2CLine.DELTA_X)
                {
                    break;
                }
                drawInfoPoint.X = SDA.PointLeft;
                drawInfoPoint.Y = SDA.Y - 2 * OFFSET_FROM_SCL;

                I2CLine.DrawInformation(g, Brushes.WhiteSmoke, actionSeries[i].Info, drawInfoPoint, drawWidth[actionSeries[i].Operation]);
                switch (actionSeries[i].Operation)
                {
                case I2CDevice.OPERATION.START:
                case I2CDevice.OPERATION.RESTART:
                    DrawStart(g);
                    break;
                case I2CDevice.OPERATION.STOP:
                    DrawStop(g);
                break;
                case I2CDevice.OPERATION.ACK:
                    DrawAck(g);
                break;
                case I2CDevice.OPERATION.NACK:
                    DrawNack(g);
                break;
                case I2CDevice.OPERATION.SEND_ADDRESS:
                case I2CDevice.OPERATION.SEND:
                    DrawSend(g, actionSeries[i].Data);
                break;
                }
                SavePosition();
                I2CLine.DrawVerticalAxe(g, SDA.PointLeft, Height);
            }
        }
        
        // ============================================================================
        /// <summary> This is called when an event occured and the display should change
        /// </summary>
        // ============================================================================
        public void Update(I2CDevice.OPERATION operation, byte data, string info)
        {
            if (_master.IsControllingSDA)
            {
                if (_master.Update(operation, data) == false)
                {
                    return;
                }
            }
            actionSeries.Add(new I2CNode(operation, data, info));
        }

        // ============================================================================
        /// <summary> This function computes the drawing coordinates according to the
        /// width and the height of the display panel  </summary>
        // ============================================================================
        void ComputeCoords(int Width, int Height)
        {
            SCL.Y = 0.9f * Height;
            SDA.Y = SCL.Y - OFFSET_FROM_SCL;
        }
        #region =================================== Draw Operations region
        // ============================================================================
        /// <summary> Draw Start condition </summary>
        // ============================================================================
        void DrawStart(Graphics g)
        {
            // ========== SDA must be high
            SDA.Draw_StraightToHigh(g);         // 1/3
            SCL.Draw_ThirdLast(g);              // 1/3
            // ========== SCL must be high
            SDA.Draw_ThirdLast(g);              // 2/3
            SCL.Draw_StraightToHigh(g);         // 2/3
            // ========== And now SDA goes low
            SDA.Draw_StraightToLow(g);          // 3/3
            SCL.Draw_TwoThirdsLast(g);          // 4/6
            // ========== Now, the Clock goes low
            SDA.Draw_TwoThirdsLast(g);          // 2/3
            SCL.Draw_StraightToLow(g);          // 5/6
            // ========== And draw a nice ending
            SDA.Draw_ThirdLast(g);              // 3/3
            SCL.Draw_ThirdLast(g);              // 6/6
        }
        // ============================================================================
        /// <summary> Draw the stop operations </summary>
        // ============================================================================
        void DrawStop(Graphics g)
        {
            // ========== SDA and SCL must be low
            SDA.Draw_StraightToLow(g);      // 1/3
            SCL.Draw_StraightToLow(g);      // 1/3
            // ========== Now, the Clock goes high
            SDA.Draw_TwoThirdsLast(g);      // 3/3
            SCL.Draw_StraightToHigh(g);     // 2/3
            // ========== And now SDA goes high
            SDA.Draw_StraightToHigh(g);     // 1/3
            SCL.Draw_TwoThirdsLast(g);      // 4/6
            // ========== And draw a nice ending
            SDA.Draw_TwoThirdsLast(g);      // 3/3
            SCL.Draw_TwoThirdsLast(g);      // 6/6
        }
        // ============================================================================
        /// <summary> Acknowledge </summary>
        // ============================================================================
        void DrawAck(Graphics g)
        {
            // ========== The Clock is performing an up-down
            SCL.Draw_Clock(g);
            // ========== The SDA gets low
            SDA.Draw_StraightToLow(g);
            SDA.Draw_TwoThirdsLast(g);
            SDA.Draw_Last(g);
        }
        // ============================================================================
        /// <summary> Not Acknowledge </summary>
        // ============================================================================
        void DrawNack(Graphics g)
        {
            // ========== The Clock is performing an up-down
            SCL.Draw_Clock(g);
            // ========== The SDA gets high
            SDA.Draw_StraightToHigh(g);
            SDA.Draw_TwoThirdsLast(g);
            SDA.Draw_Last(g);
        }
        // ============================================================================
        /// <summary> Send Data </summary>
        // ============================================================================
        void DrawSend(Graphics g, byte data)
        {
            int drawBit = 8;
            for (int bit = 0x80; bit >= 0x01; bit >>= 1)
            {
                drawBit --;
                
                drawBitPoint.Y = SCL.Y - (I2CLine.DELTA_Y / 2);
                drawBitPoint.X = SCL.LastPoint.X;
                I2CLine.DrawInformation(g, Brushes.WhiteSmoke, drawBit.ToString(), drawBitPoint, 2 * I2CLine.DELTA_X);

                drawBitPoint.Y = SDA.Y - 0.7f * I2CLine.DELTA_Y;
                drawBitPoint.X = SDA.LastPoint.X;
                SCL.Draw_Clock(g);
                // The clock is now high, I can change the SDA line
                if ((data & bit) == 0) // A zero is represented as a low
                {
                    I2CLine.DrawInformation(g, Brushes.White, "0", drawBitPoint, 2 * I2CLine.DELTA_X);
                    SDA.Draw_StraightToLow(g);
                }
                else
                {
                    I2CLine.DrawInformation(g, Brushes.White, "1", drawBitPoint, 2 * I2CLine.DELTA_X);
                    SDA.Draw_StraightToHigh(g);
                }

                
                SDA.Draw_TwoThirdsLast(g);
                SDA.Draw_Last(g);
                SDA.DrawDelimiter(g);
                SCL.DrawDelimiter(g);

                 
            }
        }
        #endregion
        #region =================================== Draw Utils
        void SavePosition()
        {
            SCL.SavePosition();
            SCL.SavePosition();
        }
        public void Clear()
        {
            actionSeries.Clear();
        }
        #endregion
        
        
    }
}
